package com.shaoyu.spring.study.validate.util;


import com.shaoyu.spring.study.validate.annotation.ParamValidate;
import com.shaoyu.spring.study.validate.annotation.ParamValidateGroup;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;

/**
 * @Description:
 * @author: shaoyu1
 * @date: 2019/8/30 15:43
 */

public class ParamValidateUtil {

    /**
     * 默认校验分组
     */
    private static String DEFAULT_GROUP = "default";

    private static LRUCache<String,ReflectInfo> cache = new LRUCache<>(100);

    private static class ReflectInfo{
        private String className;
        private Class clz;
        private List<Field> fieldListWithParamValidate;
        private Map<String,Method> fieldGetMethod;
        private Map<String,Map<String,ParamValidate>> fieldParamValidateAnnotation;
        public ReflectInfo(Class clz) {
            this.className = clz.getName();
            this.clz = clz;
            this.fieldListWithParamValidate = new ArrayList<>();
            this.fieldGetMethod = new HashMap<>();
            this.fieldParamValidateAnnotation = new HashMap<>();
        }

        public ParamValidate get(String fieldName, String group) {
            return fieldParamValidateAnnotation.getOrDefault(fieldName,new HashMap<>()).get(group);
        }
    }
    /**
     * 校验不通过返回定义的msg
     * @param param 需要校验的参数
     * @return 空字符串表示通过校验 否则返回自定义的msg
     * @throws Exception
     */
    public static String validateParam(Object param)throws Exception{
        return validateParam(param,DEFAULT_GROUP);
    }

    /**
     * @param param 需要校验的参数
     * @param group 校验参数的分组
     * @return 空字符串表示通过校验 否则返回自定义的msg
     * @throws Exception
     */
    public static String validateParam(Object param,String group)throws Exception{
        Class paramclz = param.getClass();
        ReflectInfo reflectInfo = cache.get(paramclz.getName());
        if(reflectInfo == null){
            reflectInfo = buildReflectInfo(paramclz);
        }
        List<Field> fieldListWithParamValidate = reflectInfo.fieldListWithParamValidate;
        if(CollectionUtils.isEmpty(fieldListWithParamValidate)){
            return "";
        }
        String result = "";
        for (Field field:fieldListWithParamValidate) {
            String fieldName = field.getName();
            ParamValidate paramValidate = reflectInfo.get(fieldName,group);
            if(paramValidate == null){
                continue;
            }
            Method method = reflectInfo.fieldGetMethod.get(fieldName);
            Object fieldValue = method.invoke(param);
            //校验非空
            if(paramValidate.notNull()){
                if(fieldValue==null){
                    return paramValidate.msg();
                }else if(fieldValue instanceof Collections){
                    if(CollectionUtils.isEmpty((Collection) fieldValue)){
                        return paramValidate.msg();
                    }
                }else if(fieldValue instanceof Map){
                    if(MapUtils.isEmpty((Map) fieldValue)){
                        return paramValidate.msg();
                    }
                }else if(fieldValue instanceof String){
                    if(StringUtils.isEmpty((String) fieldValue)){
                        return paramValidate.msg();
                    }
                }
            }
            ParamType type = paramValidate.type();
            //属性是实体类
            if(type.equals(ParamType.OBJECT)){
                if(fieldValue!=null){
                    result =  validateParam(fieldValue,group);
                    if(StringUtils.isNotEmpty(result)){
                        return result;
                    }
                }
            }
            //属性是实体类List
            if(type.equals(ParamType.LIST_OBJECT)){
                if(fieldValue!=null) {
                    if (fieldValue instanceof Collection) {
                        Collection c = (Collection) fieldValue;
                        for (Object o : c) {
                            result = validateParam(o,group);
                            if (StringUtils.isNotEmpty(result)) {
                                return result;
                            }
                        }
                    }
                }
            }
            //属性是简单类型List
            if(type.equals(ParamType.LIST)){
                if(fieldValue!=null){
                    //
                    if(fieldValue instanceof Collection){
                        Collection c = (Collection)fieldValue;
                        for (Object o:c) {
                            ParamType childType = paramValidate.childType();
                            result = validateObject(o,childType,paramValidate);
                            if(StringUtils.isNotEmpty(result)){
                                return result;
                            }
                        }
                    }
                }
            }
            //其他简单类型
            result = validateObject(fieldValue,type,paramValidate);
            if(StringUtils.isNotEmpty(result)){
                return result;
            }
        }
        return result;
    }

    private static ReflectInfo buildReflectInfo(Class paramclz) throws NoSuchMethodException {
        ReflectInfo reflectInfo = new ReflectInfo(paramclz);
        Field [] fields = paramclz.getDeclaredFields();
        for (Field field:fields) {
            String name = field.getName();
            if(name.equals("serialVersionUID")) {
                continue;
            }
            ParamValidate paramValidate = field.getAnnotation(ParamValidate.class);
            if(paramValidate!=null){
                fillInReflectInfo(paramclz,name,reflectInfo,field);
                Map<String,ParamValidate> groupParamValidateAnnotation = new HashMap<>();
                buildGroupParamValidateAnnotation(groupParamValidateAnnotation,paramValidate,paramclz,field);
                reflectInfo.fieldParamValidateAnnotation.put(name,groupParamValidateAnnotation);
            }
            ParamValidateGroup groupAnnotation = field.getAnnotation(ParamValidateGroup.class);
            if(groupAnnotation != null){
                fillInReflectInfo(paramclz,name,reflectInfo,field);
                Map<String,ParamValidate> groupParamValidateAnnotation = new HashMap<>();
                ParamValidate[] value = groupAnnotation.value();
                if(value!=null && value.length > 0){
                    for (ParamValidate validate : value) {
                        buildGroupParamValidateAnnotation(groupParamValidateAnnotation,validate,paramclz,field);
                    }
                }
            }
        }
        cache.put(reflectInfo.className,reflectInfo);
        return reflectInfo;
    }

    private static void buildGroupParamValidateAnnotation(Map<String, ParamValidate> groupParamValidateAnnotation, ParamValidate paramValidate, Class paramclz, Field field) {
        for (String group : paramValidate.groups()) {
            if(groupParamValidateAnnotation.get(group)!=null){
                String msg = String.format("类[%s]中校验变量[]%s含有多个相同的校验分组[%s],请检查!",
                        paramclz.getName(),
                        field.getName(),
                        group
                );
                throw new IllegalArgumentException(msg);
            }
            groupParamValidateAnnotation.put(group,paramValidate);
        }
    }

    private static void fillInReflectInfo(Class paramclz, String name, ReflectInfo reflectInfo, Field field) throws NoSuchMethodException {
        field.setAccessible(true);
        reflectInfo.fieldListWithParamValidate.add(field);
        String fieldType = field.getType().getSimpleName();
        Method method ;
        if("boolean".equals(fieldType)){
            method = paramclz.getDeclaredMethod("is"+name.substring(0,1).toUpperCase()+name.substring(1));
        }else {
            method = paramclz.getDeclaredMethod("get"+name.substring(0,1).toUpperCase()+name.substring(1));
        }
        method.setAccessible(true);
        reflectInfo.fieldGetMethod.put(name,method);
    }


    /**
     * 校验数字的小数点
     * @param fieldValue
     * @param paramValidate
     * @return
     */
    private static String validateDecimalPlaces(Object fieldValue, ParamValidate paramValidate) {
        int dep = paramValidate.decimalPlaces();
        if(dep!=Integer.MAX_VALUE){
            double doubleValue = 0;
            if(fieldValue instanceof Number){
                Number numberValue = (Number)fieldValue;
                doubleValue = numberValue.doubleValue();
            }else if(fieldValue instanceof String){
                try{
                    doubleValue = Double.parseDouble(fieldValue.toString());
                }catch (NumberFormatException e){
                    return paramValidate.msg();
                }
            }
            double doubleValueScale = new BigDecimal(doubleValue).setScale(dep,BigDecimal.ROUND_DOWN).doubleValue();
            if(doubleValue!=doubleValueScale){
                return paramValidate.msg();
            }
        }
        return "";
    }

    /**
     * 校验数字的大小
     * @param fieldValue
     * @param paramValidate
     * @return
     */
    private static String validateMaxAndMin(Object fieldValue, ParamValidate paramValidate){
        int max = paramValidate.max();
        int min = paramValidate.min();

        if(fieldValue instanceof Number){
            Number numberValue = (Number)fieldValue;
            if(numberValue.doubleValue()>max||numberValue.doubleValue()<min){
                return paramValidate.msg();
            }

        }else if(fieldValue instanceof String){
            Double doubleValue = null;
            try{
                doubleValue = Double.parseDouble(fieldValue.toString());
            }catch (NumberFormatException e){
                return paramValidate.msg();
            }
            if(doubleValue.doubleValue()>max||doubleValue.doubleValue()<min){
                return paramValidate.msg();
            }
        }
        return "";
    }

    /**
     * 校验正则表达式 只对字符串有效
     * @param fieldValue
     * @param paramValidate
     * @return
     */
    private static String validateRegexp(Object fieldValue, ParamValidate paramValidate) {
        String reg = paramValidate.regexp();
        if(fieldValue instanceof String){
            if(!reg.startsWith("^")){
                reg="^".concat(reg);
            }
            if(!reg.endsWith("$")){
                reg=reg.concat("$");
            }
            if(!fieldValue.toString().matches(reg)){
                return paramValidate.msg();
            }
        }
        return "";
    }

    /**
     * 校验长度 对字符串有效
     * @param fieldValue
     * @param paramValidate
     * @return
     */
    private static String validateLength(Object fieldValue, ParamValidate paramValidate){
        int length = paramValidate.length();
        if(fieldValue instanceof String){
            int fieldLength = fieldValue.toString().length();
            if(!(fieldLength<=length)){
                return paramValidate.msg();
            }
        }
        return "";
    }

    /**
     * 校验简单类型(数字或者字符串)
     * @param fieldValue
     * @param type
     * @param paramValidate
     * @return
     */
    private static String validateObject(Object fieldValue, ParamType type, ParamValidate paramValidate) {
        String result;
        if(type.equals(ParamType.STRING)){
            //Sting校验长度/正则表达式
            result = validateLength(fieldValue,paramValidate);
            if(StringUtils.isNotEmpty(result)){
                return result;
            }
            result = validateRegexp(fieldValue,paramValidate);
            if(StringUtils.isNotEmpty(result)){
                return result;
            }
        }
        if(type.equals(ParamType.NUMBER)){
            result = validateMaxAndMin(fieldValue,paramValidate);
            if(StringUtils.isNotEmpty(result)){
                return result;
            }
            result = validateDecimalPlaces(fieldValue,paramValidate);
            if(StringUtils.isNotEmpty(result)){
                return result;
            }
            if(fieldValue instanceof String){
                result = validateRegexp(fieldValue,paramValidate);
                if(StringUtils.isNotEmpty(result)){
                    return result;
                }
            }
        }
        return "";
    }
    public static void main(String[] args) throws Exception{
        Demo demo = new Demo();
        demo.setUserId(99L);
        demo.setChildDemo(new Demo());
        demo.setChildUserIds(Arrays.asList("123","1111","100"));
        String errorMsg = ParamValidateUtil.validateParam(demo,"userId");
        if(StringUtils.isNotEmpty(errorMsg)){
            throw new RuntimeException(errorMsg);
        }
    }


    private static class Demo{

        public Demo(String name) {
            this.name = name;
        }

        public Demo() {
        }

        @ParamValidate(groups = {"userId","default"},type = ParamType.NUMBER,max = 100,min = 10,msg = "userId不可为空,并且范围在10-100之间")
        private Long userId;

        @ParamValidate(type = ParamType.OBJECT)
        private Demo childDemo;

        @ParamValidate(type = ParamType.LIST,childType = ParamType.NUMBER,max = 100,min=10,msg = "userIds子集不可为空,并且元素范围在10-100之间")
        private List<String> childUserIds;

        @ParamValidate(type = ParamType.LIST_OBJECT)
        private List<Demo> childDemos;

        @ParamValidate(regexp = "^\\d$")
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Long getUserId() {
            return userId;
        }

        public void setUserId(Long userId) {
            this.userId = userId;
        }

        public Demo getChildDemo() {
            return childDemo;
        }

        public void setChildDemo(Demo childDemo) {
            this.childDemo = childDemo;
        }

        public List<String> getChildUserIds() {
            return childUserIds;
        }

        public void setChildUserIds(List<String> childUserIds) {
            this.childUserIds = childUserIds;
        }

        public List<Demo> getChildDemos() {
            return childDemos;
        }

        public void setChildDemos(List<Demo> childDemos) {
            this.childDemos = childDemos;
        }
    }
}